﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.ComponentModel;
using GRV11;

namespace gpuimp.core
{
    /// <summary>
    /// FileManager keeps track of all files passing into and out of the GPU-IMP application.
    /// </summary>
    [Serializable]
    public static class FileManager
    {
        private static int nextID = 0;
        private static List<ImageFile> openFiles = new List<ImageFile>();

        /// <summary>
        /// Creates a new project with deminsions (width, height) and
        /// adds it to the CanvasManager.
        /// </summary>
        /// <param name="width">width of the canvas</param>
        /// <param name="height">height of the canvas</param>
        public static int newProject(string name, int width, int height)
        {
            int canvasID;
            Bitmap newImage = new Bitmap(width, height, PixelFormat.Format24bppRgb);
            
            canvasID = CanvasManager.addCanvas(newImage, nextID, name);
            openFiles.Add(new ImageFile(nextID, name, canvasID));
            nextID++;

            return canvasID;
        }

        //public static void newProject(string name, int width, int height, 

        #region Loading

        /// <summary>
        /// Loads a recent image or project
        /// </summary>
        /// <returns>Boolean value based off success of load</returns>
        public static bool load()
        {
            return true;
        }


        /// <summary>
        /// Opens image or project at the supplied filepath.
        /// </summary>
        /// <param name="filename">name of image, including the path</param>
        /// <returns>Boolean value based off success of load</returns>
        public static int load(String fileName)
        {
            //First, confirm input is valid
            if (fileName == null || fileName == "")
            {
                return -1;
            }

            //attempt to open file
            try
            {
                //From here we would load the image from file and make a Canvas() for it.
                //Also, we will keep track of certain information of the file such as the ID, filepath
                //for easy saving.
                if(getExtension(fileName) == "imp")
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None);

                    //load basic information, canvasName, image size, number of layers
                    string canvasName = (string)formatter.Deserialize(stream);
                    int sizeX = (int)formatter.Deserialize(stream);
                    int sizeY = (int)formatter.Deserialize(stream);
                    int numLayers = (int)formatter.Deserialize(stream);
                    int numModifiers;

                    //create a new canvas
                    int canvasID = CanvasManager.addCanvas(new Canvas(new Point(sizeX, sizeY), nextID, canvasName));

                    //add a record of the file
                    openFiles.Add(new ImageFile(nextID, fileName, canvasID));

                    nextID++;

                    byte[] layerBytes;
                    Bitmap layerData;

                    //load up all the layers
		            for(int i = 0; i < numLayers; i++)
		            {
			            layerBytes = (byte[])formatter.Deserialize(stream);		    
		                layerData = new Bitmap(new MemoryStream(layerBytes));

                        CanvasManager.getCanvas(canvasID).LayerManager.addLayer(new Layer(layerData));

                        //get the number of modifiers the layer has
                        numModifiers = (int)formatter.Deserialize(stream);

                        //load up all the modifiers for the presetnt layer
                        for (int j = 0; j < numModifiers; j++)
                        {
                            string modType = (string)formatter.Deserialize(stream);

                            if (modType == "Gaussian Blur")
                            {
                                CanvasManager.getCanvas(canvasID).LayerManager.Layers[i].ModifierList.Add(new GaussianBlur());
                            }
                            else if (modType == "Edge Detect")
                            {
                                CanvasManager.getCanvas(canvasID).LayerManager.Layers[i].ModifierList.Add(new EdgeDetect());
                            }
                            else if (modType == "Mirror Plane")
                            {
                                CanvasManager.getCanvas(canvasID).LayerManager.Layers[i].ModifierList.Add(new MirrorPlane());
                            }
                            else if (modType == "Normal Map")
                            {
                                CanvasManager.getCanvas(canvasID).LayerManager.Layers[i].ModifierList.Add(new NormalMap());
                            }
                            else if (modType == "Offset")
                            {
                                CanvasManager.getCanvas(canvasID).LayerManager.Layers[i].ModifierList.Add(new Offset());
                            }

                        }

		            }
		    
                    stream.Close();

                    return canvasID;
                }
                else
                {
                    //load the image data to a bitmap
                    Bitmap imageData = (Bitmap)Bitmap.FromFile(fileName);
                    
                    int canvasID = CanvasManager.addCanvas(imageData, nextID, getFileName(fileName));
                    //add our record of a new open file
                    openFiles.Add(new ImageFile(nextID, fileName, canvasID));
                    nextID++;

                    return canvasID;
                }
            }
            catch (FileNotFoundException e)
            {
                //If an exception is thrown, we notify the user.  Should probably be a pop-up GUI element, but for now, console output!
                Console.WriteLine(e.Message);
                return -1;
            }
        }

        #endregion

        #region Saving

        /// <summary>
        /// Saves the image in the specified filepath, with the supplied format, at the quality available
        /// </summary>
        /// <param name="fileName">path on the user's computer where the image will be stored, including extension</param>
        /// <param name="quality">image quality</param>
        /// <returns>return boolean value based on the success of the file save</returns>
        [STAThread]
        public static bool save(String fileName, long quality)
        {
            bool outcome = false;
            string extension;
            Canvas projectToSave;
            ImageFile imageSaved;


            //first, confirm input is valid
            if (fileName == null || fileName == "" || quality < 0)
            {
                return false;
            }

            //find the extension of the file
            extension = getExtension(fileName);

            //if we're saving to the propreitary format
            if (extension == "imp")
            {
                //get the canvas
                Canvas canvasToSave = CanvasManager.CurrentCanvas;

                //serialize the basic information, name, image size, number of layers
                BinaryFormatter formatter = new BinaryFormatter();
                Stream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
                formatter.Serialize(stream, canvasToSave.Name);
                formatter.Serialize(stream, canvasToSave.ImageSize.X);
                formatter.Serialize(stream, canvasToSave.ImageSize.Y);
                formatter.Serialize(stream, canvasToSave.LayerManager.Layers.Count);

                TypeConverter converter = TypeDescriptor.GetConverter(typeof(Bitmap));

		        byte[] layerData;		
        		
                //save each layer
		        for(int i = 0; i < canvasToSave.LayerManager.Layers.Count; i++)
		        {
			        layerData = (byte[])converter.ConvertTo(canvasToSave.LayerManager.Layers[i].BaseTexture, typeof(byte[]));

                    formatter.Serialize(stream, layerData);

                    formatter.Serialize(stream, canvasToSave.LayerManager.Layers[i].ModifierList.Count);

                    //now go through and save the modifiers for each layer
                    for (int j = 0; j < canvasToSave.LayerManager.Layers[i].ModifierList.Count; j++)
                    {
                        formatter.Serialize(stream, canvasToSave.LayerManager.Layers[i].ModifierList[j].getName());
                    }
		        }

                stream.Close();

                //TODO: designed format save process
                //I'm thinking of a format around this, all lines would be seperated by delimiters
                //FILENAME
                //START LAYER FIELD
                //  LAYER NAME
                //      POSITION
                //      VISIBLE
                //      LOCKED
                //      PIXEL DATA
                //      START MODIFIER FIELD
                //          MODIFIER NAME
                //              DATA
                //              END MODIFIER
                //          MODIFIER NAME
                //              DATA
                //              END MODIFIER
                //              .
                //              .
                //              .
                //      END MODIFIER FIELD
                //      END LAYER
                //  LAYER NAME
                //      POSITION
                //      VISIBLE
                //      LOCKED
                //      PIXEL DATA
                //      START MODIFIER FIELD
                //          MODIFIER NAME
                //              DATA
                //              END MODIFIER
                //          MODIFIER NAME
                //              DATA
                //              END MODIFIER
                //              .
                //              .
                //              .
                //      END MODIFIER FIELD
                //      END LAYER
                //   .
                //   .
                //   .
                //END LAYER FIELD
                //EOF
            }
            else //if not, then export it to the desired format
            {
                //find the available encoders on the user's computer
                ImageCodecInfo encoderInfo = getEncoderInfo(extension);

                if (encoderInfo == null)
                {
                    return false;
                }

                //construct the parameters for the encoder
                EncoderParameter qualityParameter = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
                EncoderParameters encoderParams = new EncoderParameters(1);
                encoderParams.Param[0] = qualityParameter;

                //get the canvas
                projectToSave = CanvasManager.CurrentCanvas;

                ImageFormat format;

                //find the imageformat to use
                if (extension == "jpeg")
                {
                    format = ImageFormat.Jpeg;
                }
                else if (extension == "bmp")
                {
                    format = ImageFormat.Bmp;
                }
                else if (extension == "gif")
                {
                    format = ImageFormat.Gif;
                }
                else if (extension == "png")
                {
                    format = ImageFormat.Png;
                }
                else
                {
                    format = ImageFormat.Jpeg;
                }

                //transparent color
                Color transColor = new Color();
                transColor = Color.FromArgb(0, 255, 255, 255);

                //if there are multiple layers, merge the layers to one flat image
                Bitmap finalImage = getMergedImage(projectToSave.LayerManager, transColor);

                //and save the final image
                finalImage.Save(fileName, encoderInfo, encoderParams);

                //now update the appropriate imageFile
                imageSaved = openFiles.Find(delegate(ImageFile f)
                {
                    return f.getCanvasID() == projectToSave.ID;
                });

                //if not found
                if (imageSaved == null)
                {
                    imageSaved = new ImageFile(projectToSave.FileID, projectToSave.Name, projectToSave.ID);
                    openFiles.Add(imageSaved);
                }

                imageSaved.setFilePath(fileName);
                imageSaved.setQuality(quality);

                //now return the result
                outcome = true;
            }

            return outcome;
        }

        /// <summary>
        /// Autosaves the current canvas, then returns a boolean value based on success or fail
        /// </summary>
        /// <returns>Boolean value based on whether the file has been saved already, if not, open a save as dialog</returns>
        public static bool autoSave()
        {
            int canvasID;
            ImageFile currentFile;
            bool isFound = false;
            string extension;

            canvasID = CanvasManager.getCurrentCanvas();
            currentFile = openFiles.Find(delegate(ImageFile f)
            {
                if (f.getCanvasID() == canvasID && f.getFilePath() != "")
                {
                    isFound = true;    
                }

                return f.getCanvasID() == canvasID;                
            });

            if (!isFound)
            {
                //file hasn't been saved before, so we ask them to save it.
                Microsoft.Win32.SaveFileDialog saveDialog = new Microsoft.Win32.SaveFileDialog();
                saveDialog.Filter = "IMP Project (*.imp)|*.imp|JPEG (*.jpeg)|.jpeg|GIF (*.gif)|.gif|PNG (*.png)|.png|Bitmap (*.bmp)|*.bmp|All Files (*.*)|*.*";
                saveDialog.ShowDialog();

                return save(saveDialog.FileName, (long)100);
            }
            else
            {
                //find the extension of the file
                extension = getExtension(currentFile.getFilePath());

                //if we're exporting to a particular format:
                if (extension == "imp")
                {
                    //put together the save file
                    Canvas canvasToSave = CanvasManager.CurrentCanvas;

                    BinaryFormatter formatter = new BinaryFormatter();
                    Stream stream = new FileStream(currentFile.getFilePath(), FileMode.Create, FileAccess.Write, FileShare.None);
                    formatter.Serialize(stream, canvasToSave.Name);
                    formatter.Serialize(stream, canvasToSave.ImageSize.X);
                    formatter.Serialize(stream, canvasToSave.ImageSize.Y);
                    formatter.Serialize(stream, canvasToSave.LayerManager.Layers.Count);

                    TypeConverter converter = TypeDescriptor.GetConverter(typeof(Bitmap));

                    byte[] layerData;

                    //save each layer
                    for (int i = 0; i < canvasToSave.LayerManager.Layers.Count; i++)
                    {
                        layerData = (byte[])converter.ConvertTo(canvasToSave.LayerManager.Layers[i].BaseTexture, typeof(byte[]));

                        formatter.Serialize(stream, layerData);

                        formatter.Serialize(stream, canvasToSave.LayerManager.Layers[i].ModifierList.Count);

                        //now go through and save the modifiers for each layer
                        for (int j = 0; j < canvasToSave.LayerManager.Layers[i].ModifierList.Count; j++)
                        {
                            formatter.Serialize(stream, canvasToSave.LayerManager.Layers[i].ModifierList[j]);
                        }
                    }

                    stream.Close();

                    return true;
                }
                else //if not, then export it to the desired format
                {
                    //find the available encoders on the user's computer
                    ImageCodecInfo encoderInfo = getEncoderInfo(extension);

                    if (encoderInfo == null)
                    {
                        return false;
                    }

                    //construct the parameters for the encoder
                    EncoderParameter qualityParameter = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, currentFile.getQuality());
                    EncoderParameters encoderParams = new EncoderParameters(1);
                    encoderParams.Param[0] = qualityParameter;

                    //for now, just a single layer, multi-layer to come!
                    CanvasManager.getCanvas(canvasID).LayerManager.Layers[1].BaseTexture.Save(currentFile.getFilePath(), encoderInfo, encoderParams);

                    return true;
                }
            }

            

        }

        #endregion

        /// <summary>
        /// Closes the open file
        /// </summary>
        /// <param name="cID">canvas ID</param>
        /// <returns>boolean value based off of success or failure</returns>
        public static bool closeFile(int cID)
        {
            if (!openFiles.Exists(delegate(ImageFile f) { return f.getCanvasID() == cID; }))
            {
                return false;
            }

            //TODO: check to see if there are unsaved changes
            //ALGORITHM:  imageFile keeps track of most recent state (obtainable by getting the most recent 
            //state from Canvas's StateManager). if the now most recent state is more recent, ask the user
            //if they want to save the image before closing.
            return openFiles.Remove(openFiles.Find(delegate(ImageFile f) { return f.getCanvasID() == cID; }));
        }


        #region Helper Methods

        /// <summary>
        /// Determines the file's name, without filepath and extension
        /// </summary>
        /// <param name="fileName">full file's name, including path and extension</param>
        /// <returns>the file's name</returns>
        private static string getFileName(string fileName)
        {
            string[] splitName;
            string[] splitNameExtension;
            string[] directoryDelimiter = new string[1];
                     directoryDelimiter[0] = "\\";
            string nameWithExtension = "";
            string nameWithoutExtension = "";

            if (fileName == null || fileName == "")
            {
                return null;
            }

            //seperate the directories
            splitName = fileName.Split(directoryDelimiter , StringSplitOptions.None);
            nameWithExtension = splitName[splitName.Length - 1];

            //seperate the extension
            splitNameExtension = nameWithExtension.Split('.');

            for (int i = 0; i < splitNameExtension.Length - 1; i++)
            {
                if (i > 0)
                {
                    nameWithoutExtension = nameWithoutExtension + ".";
                }
                nameWithoutExtension = nameWithoutExtension + splitNameExtension[i];
            }

            return nameWithoutExtension;
        }

        /// <summary>
        /// Determines a file's extension, given the file's name
        /// </summary>
        /// <param name="fileName">Name of the file</param>
        /// <returns>The file's extension</returns>
        private static string getExtension(string fileName)
        {
            if (fileName == null || fileName == "")
            {
                return null;
            }

            string []splitName = fileName.Split('.');
            string extension = splitName[splitName.Length - 1];

            if (extension == "jpg")
            {
                extension = "jpeg";
            }

            return extension;
        }   

        /// <summary>
        /// Helper method for finding if a specific codec is available on the user's computer
        /// </summary>
        /// <param name="codec">The name of the query'd codec</param>
        /// <returns>The information of the codec, or null if none is found</returns>
        private static ImageCodecInfo getEncoderInfo(string codec)
        {
            String codecSuffix = "image/";

            // Get image codecs for all image formats available
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();

            //correct format if necessary
            String formattedCodec = String.Concat(codecSuffix, codec);

            // Find the correct image codec
            for (int i = 0; i < codecs.Length; i++)
            {
                if (codecs[i].MimeType == formattedCodec)
                {
                    return codecs[i];
                }
            }

            return null;
        }

        /// <summary>
        /// Helper Method that converts an object to an array of bytes.
        /// </summary>
        /// <param name="o">Object to be serialized into a byte array</param>
        /// <returns>The serialized byte array of the Object</returns>
        public static byte[] getByteArrayFromObject(Object o)
        {
            MemoryStream mStream = new MemoryStream();
            BinaryFormatter bFormatter = new BinaryFormatter();

            bFormatter.Serialize(mStream, o);

            return mStream.ToArray();
        }

        /// <summary>
        /// Helper Method that converts a byte stream into it's Object.
        /// </summary>
        /// <param name = "byteArray">the byte array for deserialization</param>
        /// <returns>The deserialized Object</returns>
        public static object getObjectFromByteArray(byte[] byteArray)
        {
            MemoryStream mStream = new MemoryStream(byteArray);
            BinaryFormatter bFormatter = new BinaryFormatter();

            mStream.Position = 0;

            return bFormatter.Deserialize(mStream);
        }

        /// <summary>
        /// Helper Method that reads in all layers and merges them to a single Bitmap Object
        /// </summary>
        /// <param name="layerManager">The Canvas's LayerManager</param>
        /// <returns>The Merged Bitmap Image</returns>
        public static Bitmap getMergedImage(LayerManager layerManager, Color transparentColor)
        {
            int width = layerManager.Layers[0].BaseTexture.Width;
            int height = layerManager.Layers[0].BaseTexture.Height;
            int numLayers = layerManager.Layers.Count;
            Bitmap presentBitmap;
            Bitmap finalImage = new Bitmap(width, height);


            //ouch
            //basically, the way this monstrosity works is we go through all the layers, copying all
            //non-transparent data to the flatten image.  we go through the list of layers this way.
            //the result is a single layer bitmap object that is a flattened image of the current canvas.
            for (int i = 0; i < numLayers; i++)
            {
                if (layerManager.Layers[i].IsVisible == true)
                {
                    if (layerManager.Layers[i].ModifierList.Count > 0)
                    {
                        presentBitmap = layerManager.Layers[i].FinalTexture;
                    }
                    else
                    {
                        presentBitmap = layerManager.Layers[i].BaseTexture;
                    }

                    for (int j = 0; j < width; j++)
                    {
                        for (int k = 0; k < height; k++)
                        {
                            Color color;
                            if ((color = presentBitmap.GetPixel(j, k)) != transparentColor)
                            {
                                finalImage.SetPixel(j, k, presentBitmap.GetPixel(j, k));
                            }
                        }
                    }
                }  
            }

            return finalImage;
        }


        #endregion


        public static int getNumOpenFiles()
        {
            return openFiles.Count;
        }

        public static string getFileName(int canvasID)
        {
            return getFileName(openFiles.Find(delegate(ImageFile f) { return f.getCanvasID() == canvasID; }).getFilePath());
        }
    }
}
